This notebook compares doublet results calculated on benchmarking datasets to one another, with the primary goal of addressing these questions:

  1. Do methods tend to predict overlapping or distinct sets of doublets on the same dataset?
  2. Is the consensus doublet call across methods predictive of the true doublet status? Here, the “consensus doublets” are those droplets which all methods identify as doublets.

There are several items to bear in mind when interpreting these results:

Setup

Packages

suppressPackageStartupMessages({
  library(SingleCellExperiment)
  library(ggplot2)
  library(patchwork)
  library(caret)
  library(UpSetR)
})

theme_set(theme_bw())

# define threshold used to call cxds
cxds_threshold <- 0.5

Paths

module_base <- rprojroot::find_root(rprojroot::is_renv_project)
data_dir <- file.path(module_base, "scratch", "benchmark-datasets")
result_dir <- file.path(module_base, "results", "benchmark-results")

Functions

plot_pca_calls <- function(df, 
                           color_column, 
                           plot_type = "calls",
                           title) {
  # Plot PCs colored by either:
  # if plot_type == "calls", color by singlet or doublet, showing doublets on top
  # if plot_type == "class", color by singlet, doublet, or ambiguous, showing doublets & ambiguous on top
  # df is expected to contain columns PC1, PC2, `color_column`, which should _not_ be provided as a string
  plot <- ggplot(df) + 
    aes(
      x = PC1, 
      y = PC2, 
      color = {{color_column}}
    ) +
    geom_point(
      size = 0.75, 
      alpha = 0.6
    ) +
    ggtitle(title) +
    guides(color = guide_legend(override.aes = list(size = 2)))
  
  if (plot_type == "calls") {
    plot <- plot + 
      scale_color_manual(
        name = "", 
        values = c("doublet" = "black", "singlet" = "gray90")) + 
      geom_point(
        data = dplyr::filter(df, {{color_column}} == "doublet"), 
        color = "black",
        size = 0.75
      )
  } else if (plot_type == "class") {
    plot <- plot + 
      scale_color_manual(
        name = "Consensus", 
        values = c("ambiguous" = "yellow", 
                   "doublet" = "black", 
                   "singlet" = "gray90")
      ) + 
      geom_point(
        data = dplyr::filter(df, consensus_class != "singlet"), 
        size = 0.75,
        alpha = 0.7,
        aes(color = consensus_class),
      )
  } else {
    stop("plot_type must be 'calls' or 'class'")
  }
  
  return(plot)
}


plot_pca_metrics <- function(df, color_column) {
  # Plot PCs colored by performance metric, showing false calls on top
  # metric_colors is a named vector of colors used for coloring tp/tn/fp/fn

  # used in PCA plots
  metric_colors <- c(
    "tp" = "lightblue",
    "tn" = "pink",
    "fp" = "blue",
    "fn" = "firebrick2"
  )

  ggplot(df) + 
    aes(x = PC1, 
        y = PC2, 
        color = {{color_column}}) +
  geom_point(
    size = 0.75, 
    alpha = 0.6
  ) + 
  geom_point(
    data = dplyr::filter(df, {{color_column}} %in% c("fp", "fn")), 
    size = 0.75
  ) +
  scale_color_manual(name = "Call type", values = metric_colors) +
  ggtitle("Consensus class metrics") +
  guides(color = guide_legend(override.aes = list(size = 2)))

}

Read and prepare input data

First, we’ll read in and combine doublet results, as well as PCA coordinates, into a list of data frames for each dataset.

# find all dataset names to process:
dataset_names <- list.files(result_dir, pattern = "*_scrublet.tsv") |>
  stringr::str_remove("_scrublet.tsv")
# Read in data for analysis
doublet_df_list <- dataset_names |>
  purrr::map(
    \(dataset) {
      
      scdbl_tsv <- file.path(result_dir, glue::glue("{dataset}_scdblfinder.tsv"))
      scrub_tsv <- file.path(result_dir, glue::glue("{dataset}_scrublet.tsv"))
      sce_file <- file.path(data_dir, dataset, glue::glue("{dataset}_sce.rds"))
      
      scdbl_df <- scdbl_tsv |>
        readr::read_tsv(show_col_types = FALSE) |>
        dplyr::select(
          barcodes,
          cxds_score, 
          scdbl_score = score, 
          scdbl_prediction  = class
        ) |>
        # add cxds calls at `cxds_threshold` threshold
        dplyr::mutate(
          cxds_prediction = dplyr::if_else(
            cxds_score >= cxds_threshold,
            "doublet",
            "singlet"
          )
        ) 
      
      scrub_df <- readr::read_tsv(scrub_tsv, show_col_types = FALSE) 

      # grab ground truth and PCA coordinates
      sce <- readr::read_rds(sce_file)
      
      scuttle::makePerCellDF(sce, use.dimred = "PCA") |>
        tibble::rownames_to_column(var = "barcodes") |>
        dplyr::select(barcodes,
                      ground_truth = ground_truth_doublets, 
                      PC1 = PCA.1, 
                      PC2 = PCA.2) |> 
        dplyr::left_join(
          scrub_df, 
          by = "barcodes"
        ) |>
        dplyr::left_join(
          scdbl_df, 
          by = "barcodes"
        ) 
    } 
  ) |> 
  purrr::set_names(dataset_names)

Upset plots

This section contains upset plots that show overlap across doublet calls from each method, displayed for each dataset.

doublet_df_list |>
  purrr::iwalk(
    \(df, dataset) {
      
      doublet_barcodes <- list(
        "scDblFinder" = df$barcodes[df$scdbl_prediction == "doublet"],
        "scrublet"    = df$barcodes[df$scrublet_prediction == "doublet"],
        "cxds"        = df$barcodes[df$cxds_prediction == "doublet"]
      )
      
      UpSetR::upset(fromList(doublet_barcodes), order.by = "freq") |> print()
      grid::grid.text( # plot title
        dataset,
        x = 0.65, 
        y = 0.95, 
        gp = grid::gpar(fontsize=16)
      ) 

    }
  )

Explore consensus across methods

Next, we will explore the consensus of doublet predictions across methods. To this end, we’ll create some new columns for each dataset:

doublet_df_list <- doublet_df_list |>
  purrr::map(
    \(dataset_df) {
      
      dataset_df |>
        dplyr::rowwise() |>

        dplyr::mutate(
          # Add column `consensus_call`
          # "doublet" if all three methods are doublet, and "singlet" otherwise          
          consensus_call = dplyr::if_else(
            all(c(scdbl_prediction, scrublet_prediction, cxds_prediction) == "doublet"),
            "doublet", 
            "singlet"
          ),
          # Add column `consensus_class`
          #  "doublet" if all three methods are doublet, "singlet" if all three methods are singlet, 
          #  and "ambiguous" if there are any disagreements
          consensus_class = dplyr::case_when(
            consensus_call == "doublet" ~ "doublet",
            all(c(scdbl_prediction, scrublet_prediction, cxds_prediction) == "singlet") ~ "singlet", 
            .default = "ambiguous"
          ), 
          # Add column `confusion_call`
          #  tp/tn/fp/fn based on the `consensus_call` column
          confusion_call = dplyr::case_when(
            consensus_call == "doublet" && ground_truth == "doublet" ~ "tp",
            consensus_call == "singlet" && ground_truth == "singlet" ~ "tn",
            consensus_call == "doublet" && ground_truth == "singlet" ~ "fp",
            consensus_call == "singlet" && ground_truth == "doublet" ~ "fn"
          )
        )
      }
  ) 

PCA

This section plots the PCA for each dataset, clockwise from the top left:

  1. scDblFinder singlet/doublet calls
  2. scrublet singlet/doublet calls
  3. cxds singlet/doublet calls
  4. Ground truth single/doublets
  5. Consensus call droplets are either “doublet” or “singlet”
  6. Consensus class: droplets are either “doublet”, “singlet”, or “ambiguous”
  7. Metrics based on the consensus call
  • For the first five panels, doublets are shown in black and singlets in light gray.
  • In the sixth panel showing the consensus class, doublets are shown in black, singlets in light gray, and ambiguous are yellow
  • In the seventh panel showing metrics, colors are:
    • True positive: light blue
    • True negative: pink
    • False positive: blue
    • False negative: red
pc_color_columns <- c("scDblFinder prediction" = "scdbl_prediction",
                      "scrublet prediction" = "scrublet_prediction",
                      "cxds prediction" = "cxds_prediction",
                      "Ground truth" = "ground_truth", 
                      "Consensus call" = "consensus_call")
doublet_df_list |>
  purrr::imap(
    \(df, dataset) {
  
      pc_plot_list <- pc_color_columns |> 
        purrr::imap(
          \(color_column, plot_title) {
            plot_pca_calls(
              df, 
              color_column = !!sym(color_column), 
              plot_type = "calls",
              title = plot_title
            ) + theme(legend.position = "none")}
        ) 

      # plot for consensus_class, which needs three colors
      # note that the list name isn't actually used here
      pc_plot_list$consensus_class <- plot_pca_calls(
        df, 
        color_column = consensus_class,
        plot_type = "class",
        title = "Consensus class"
      )
      
      # plot metrics
      pc_plot_list$metrics <- plot_pca_metrics(
        df, 
        color_column = confusion_call
      )

      patchwork::wrap_plots(pc_plot_list) +
        plot_annotation(
            glue::glue("PCA for {dataset}"), 
            theme = theme(plot.title = element_text(size = 16))) + 
        plot_layout(guides = "collect")
    }
  )
$`hm-6k`

$`HMEC-orig-MULTI`

$`pbmc-1B-dm`

$`pdx-MULTI`

Performance metrics

This section shows a table of the consensus class counts and calculates a confusion matrix with associated statistics from the consensus calls:

metric_df <- doublet_df_list |>
  purrr::imap( 
    \(df, dataset) {
        print(glue::glue("======================== {dataset} ========================"))
      
        cat("Table of consensus class counts:")
        print(table(df$consensus_class))
        
        cat("\n\n")
        
        confusion_result <- caret::confusionMatrix(
          # truth should be first
          table(
            "Truth" = df$ground_truth,
            "Consensus prediction" = df$consensus_call
          ), 
          positive = "doublet"
        ) 
        
        print(confusion_result)
        
        # Extract information we want to present later in a table
        tibble::tibble(
          "Dataset name" = dataset,
          "Kappa" = round(confusion_result$overall["Kappa"], 3), 
          "Balanced accuracy" = round(confusion_result$byClass["Balanced Accuracy"], 3)
        )
    }
  ) |>
  dplyr::bind_rows()
======================== hm-6k ========================
Table of consensus class counts:
ambiguous   doublet   singlet 
      283       138      6385 


Confusion Matrix and Statistics

         Consensus prediction
Truth     doublet singlet
  doublet     138      33
  singlet       0    6635
                                          
               Accuracy : 0.9952          
                 95% CI : (0.9932, 0.9967)
    No Information Rate : 0.9797          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.8908          
                                          
 Mcnemar's Test P-Value : 2.54e-08        
                                          
            Sensitivity : 1.00000         
            Specificity : 0.99505         
         Pos Pred Value : 0.80702         
         Neg Pred Value : 1.00000         
             Prevalence : 0.02028         
         Detection Rate : 0.02028         
   Detection Prevalence : 0.02512         
      Balanced Accuracy : 0.99753         
                                          
       'Positive' Class : doublet         
                                          
======================== HMEC-orig-MULTI ========================
Table of consensus class counts:
ambiguous   doublet   singlet 
     3206       641     22579 


Confusion Matrix and Statistics

         Consensus prediction
Truth     doublet singlet
  doublet     506    3062
  singlet     135   22723
                                         
               Accuracy : 0.879          
                 95% CI : (0.875, 0.8829)
    No Information Rate : 0.9757         
    P-Value [Acc > NIR] : 1              
                                         
                  Kappa : 0.2079         
                                         
 Mcnemar's Test P-Value : <2e-16         
                                         
            Sensitivity : 0.78939        
            Specificity : 0.88125        
         Pos Pred Value : 0.14182        
         Neg Pred Value : 0.99409        
             Prevalence : 0.02426        
         Detection Rate : 0.01915        
   Detection Prevalence : 0.13502        
      Balanced Accuracy : 0.83532        
                                         
       'Positive' Class : doublet        
                                         
======================== pbmc-1B-dm ========================
Table of consensus class counts:
ambiguous   doublet   singlet 
      487        38      3265 


Confusion Matrix and Statistics

         Consensus prediction
Truth     doublet singlet
  doublet      26     104
  singlet      12    3648
                                          
               Accuracy : 0.9694          
                 95% CI : (0.9634, 0.9746)
    No Information Rate : 0.99            
    P-Value [Acc > NIR] : 1               
                                          
                  Kappa : 0.2986          
                                          
 Mcnemar's Test P-Value : <2e-16          
                                          
            Sensitivity : 0.68421         
            Specificity : 0.97228         
         Pos Pred Value : 0.20000         
         Neg Pred Value : 0.99672         
             Prevalence : 0.01003         
         Detection Rate : 0.00686         
   Detection Prevalence : 0.03430         
      Balanced Accuracy : 0.82825         
                                          
       'Positive' Class : doublet         
                                          
======================== pdx-MULTI ========================
Table of consensus class counts:
ambiguous   doublet   singlet 
     2945         4      7347 


Confusion Matrix and Statistics

         Consensus prediction
Truth     doublet singlet
  doublet       3    1314
  singlet       1    8978
                                          
               Accuracy : 0.8723          
                 95% CI : (0.8657, 0.8787)
    No Information Rate : 0.9996          
    P-Value [Acc > NIR] : 1               
                                          
                  Kappa : 0.0038          
                                          
 Mcnemar's Test P-Value : <2e-16          
                                          
            Sensitivity : 0.7500000       
            Specificity : 0.8723280       
         Pos Pred Value : 0.0022779       
         Neg Pred Value : 0.9998886       
             Prevalence : 0.0003885       
         Detection Rate : 0.0002914       
   Detection Prevalence : 0.1279138       
      Balanced Accuracy : 0.8111640       
                                          
       'Positive' Class : doublet         
                                          

Conclusions

Overall, methods do not have substantial overlap with each other. They each tend to detect different sets of doublets, leading to fairly small sets of consensus doublets. Further, the consensus doublets called by all three methods have some, but not substantial, overlap with the ground truth.

For three out of four datasets, scDblFinder predicts a much larger number of doublets compared to other methods.

Based on the PCAs, it additionally looks like there are many more false negatives in the consensus prediction that cxds appears to capture but other methods do not.

The table below summarizes performance of the “consensus caller”. Note that, in the benchmarking paper these datasets were originally analyzed in, hm-6k was observed to be one of the “easiest” datasets to classify across methods.
Consistent with that observation, it has the highest kappa value here.

metric_df

Explore consensus of scDblFinder and cxds

The above analysis suggests that scrublet may be more conservative than the other two methods, so including it in a consensus may be increasing the number of false positives. Here, we’ll repeat the same consensus analyses, but considering only scDblFinder and cxds. Since there are only two methods here, we will create two columns for this data:

Prepare data

# modify existing consensus columns to only consider 2 methods
doublet_df_list <- doublet_df_list |>
  purrr::map(
    \(dataset_df) {
      
      dataset_df <- dataset_df |>
        dplyr::rowwise() |>
        # Update column `consensus_call`
        dplyr::mutate(
          consensus_call = dplyr::if_else(
            all(c(scdbl_prediction, cxds_prediction) == "doublet"),
            "doublet", 
            "singlet"
          )
        ) |>
        # Add Update `consensus_class`
        dplyr::mutate(
          consensus_class = dplyr::case_when(
            consensus_call == "doublet" ~ "doublet",
            all(c(scdbl_prediction, cxds_prediction) == "singlet") ~ "singlet", 
            .default = "ambiguous"
          )
         ) |>
        # Add Update `confusion_call`
        dplyr::mutate(
          confusion_call = dplyr::case_when(
            consensus_call == "doublet" && ground_truth == "doublet" ~ "tp",
            consensus_call == "singlet" && ground_truth == "singlet" ~ "tn",
            consensus_call == "doublet" && ground_truth == "singlet" ~ "fp",
            consensus_call == "singlet" && ground_truth == "doublet" ~ "fn"
          )
        )
      
      return(dataset_df)
    }
  ) |> 
  purrr::set_names(dataset_names)

PCA

This section plots the PCA for each dataset, clockwise from the top left:

  1. scDblFinder singlet/doublet calls
  2. cxds singlet/doublet calls
  3. Ground truth single/doublets
  4. Consensus call droplets are either “doublet” or “singlet”
  5. Consensus class: droplets are either “doublet”, “singlet”, or “ambiguous”
  6. Metrics based on the consensus call
pc_color_columns <- c("scDblFinder prediction" = "scdbl_prediction",
                      "cxds prediction" = "cxds_prediction",
                      "Ground truth" = "ground_truth", 
                      "Consensus call" = "consensus_call")

doublet_df_list |>
  purrr::imap(
    \(df, dataset) {
      
      pc_plot_list <- pc_color_columns |> 
        purrr::imap(
          \(color_column, plot_title) {
            plot_pca_calls(
              df, 
              color_column = !!sym(color_column), 
              plot_type = "calls",
              title = plot_title
            ) + theme(legend.position = "none")}
        ) 
      # plot for consensus_class, which needs three colors
      # note that the list name isn't actually used here
      pc_plot_list$consensus_class <- plot_pca_calls(
        df, 
        color_column = consensus_class,
        plot_type = "class",
        title = "Consensus class"
      )

      # plot metrics
      pc_plot_list$metrics <- plot_pca_metrics(
        df, 
        color_column = confusion_call
      )
      
      patchwork::wrap_plots(pc_plot_list) +
        plot_annotation(
            glue::glue("PCA for {dataset}"), 
            theme = theme(plot.title = element_text(size = 16))) + 
        plot_layout(guides = "collect")
    }
  )
$`hm-6k`

$`HMEC-orig-MULTI`

$`pbmc-1B-dm`

$`pdx-MULTI`

Performance metrics

This section shows a table of the consensus class counts and calculates a confusion matrix with associated statistics from the consensus calls:

metric_df <- doublet_df_list |>
  purrr::imap( 
    \(df, dataset) {
        print(glue::glue("======================== {dataset} ========================"))
      
        cat("Table of consensus class counts:")
        print(table(df$consensus_class))
        
        cat("\n\n")
        
        confusion_result <- caret::confusionMatrix(
          # truth should be first
          table(
            "Truth" = df$ground_truth,
            "Consensus prediction" = df$consensus_call
          ), 
          positive = "doublet"
        ) 
        
        print(confusion_result)
        
        # Extract information we want to present later in a table
        tibble::tibble(
          "Dataset name" = dataset,
          "Kappa" = round(confusion_result$overall["Kappa"], 3), 
          "Balanced accuracy" = round(confusion_result$byClass["Balanced Accuracy"], 3)
        )
    }
  ) |>
  dplyr::bind_rows()
======================== hm-6k ========================
Table of consensus class counts:
ambiguous   doublet   singlet 
      274       144      6388 


Confusion Matrix and Statistics

         Consensus prediction
Truth     doublet singlet
  doublet     144      27
  singlet       0    6635
                                          
               Accuracy : 0.996           
                 95% CI : (0.9942, 0.9974)
    No Information Rate : 0.9788          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.9123          
                                          
 Mcnemar's Test P-Value : 5.624e-07       
                                          
            Sensitivity : 1.00000         
            Specificity : 0.99595         
         Pos Pred Value : 0.84211         
         Neg Pred Value : 1.00000         
             Prevalence : 0.02116         
         Detection Rate : 0.02116         
   Detection Prevalence : 0.02512         
      Balanced Accuracy : 0.99797         
                                          
       'Positive' Class : doublet         
                                          
======================== HMEC-orig-MULTI ========================
Table of consensus class counts:
ambiguous   doublet   singlet 
     2791       986     22649 


Confusion Matrix and Statistics

         Consensus prediction
Truth     doublet singlet
  doublet     776    2792
  singlet     210   22648
                                          
               Accuracy : 0.8864          
                 95% CI : (0.8825, 0.8902)
    No Information Rate : 0.9627          
    P-Value [Acc > NIR] : 1               
                                          
                  Kappa : 0.2999          
                                          
 Mcnemar's Test P-Value : <2e-16          
                                          
            Sensitivity : 0.78702         
            Specificity : 0.89025         
         Pos Pred Value : 0.21749         
         Neg Pred Value : 0.99081         
             Prevalence : 0.03731         
         Detection Rate : 0.02937         
   Detection Prevalence : 0.13502         
      Balanced Accuracy : 0.83863         
                                          
       'Positive' Class : doublet         
                                          
======================== pbmc-1B-dm ========================
Table of consensus class counts:
ambiguous   doublet   singlet 
      323       168      3299 


Confusion Matrix and Statistics

         Consensus prediction
Truth     doublet singlet
  doublet      64      66
  singlet     104    3556
                                          
               Accuracy : 0.9551          
                 95% CI : (0.9481, 0.9615)
    No Information Rate : 0.9557          
    P-Value [Acc > NIR] : 0.582698        
                                          
                  Kappa : 0.4066          
                                          
 Mcnemar's Test P-Value : 0.004543        
                                          
            Sensitivity : 0.38095         
            Specificity : 0.98178         
         Pos Pred Value : 0.49231         
         Neg Pred Value : 0.97158         
             Prevalence : 0.04433         
         Detection Rate : 0.01689         
   Detection Prevalence : 0.03430         
      Balanced Accuracy : 0.68137         
                                          
       'Positive' Class : doublet         
                                          
======================== pdx-MULTI ========================
Table of consensus class counts:
ambiguous   doublet   singlet 
     2272       676      7348 


Confusion Matrix and Statistics

         Consensus prediction
Truth     doublet singlet
  doublet     328     989
  singlet     348    8631
                                          
               Accuracy : 0.8701          
                 95% CI : (0.8635, 0.8766)
    No Information Rate : 0.9343          
    P-Value [Acc > NIR] : 1               
                                          
                  Kappa : 0.2654          
                                          
 Mcnemar's Test P-Value : <2e-16          
                                          
            Sensitivity : 0.48521         
            Specificity : 0.89719         
         Pos Pred Value : 0.24905         
         Neg Pred Value : 0.96124         
             Prevalence : 0.06566         
         Detection Rate : 0.03186         
   Detection Prevalence : 0.12791         
      Balanced Accuracy : 0.69120         
                                          
       'Positive' Class : doublet         
                                          

Conclusions

Considering only scDblFinder and cxds, we see several particular differences in statistics:

  • Balanced accuracy for hm-6k and HMEC-orig-MULTI are about the same here as for the consensus among all methods, but accuracy has substantially decreased for pbmc-1B-dm and pdx-MULTI1
  • By contrast, kappa values have increased for all datasets, with the most marked increase for pdx-MULTI.

Even so, only hm-6k (the “easy” dataset) has both a high kappa and high balanced accuracy. While HMEC-orig-MULTI’s balanced accuracy is fairly high, its kappa value is not.

metric_df

Session Info

# record the versions of the packages used in this analysis and other environment information
sessionInfo()
R version 4.4.0 (2024-04-24)
Platform: aarch64-apple-darwin20
Running under: macOS Sonoma 14.5

Matrix products: default
BLAS:   /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.0

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: America/New_York
tzcode source: internal

attached base packages:
[1] stats4    stats     graphics  grDevices datasets  utils     methods   base     

other attached packages:
 [1] UpSetR_1.4.0                caret_6.0-94               
 [3] lattice_0.22-6              patchwork_1.2.0            
 [5] ggplot2_3.5.1               SingleCellExperiment_1.26.0
 [7] SummarizedExperiment_1.34.0 Biobase_2.64.0             
 [9] GenomicRanges_1.56.0        GenomeInfoDb_1.40.0        
[11] IRanges_2.38.0              S4Vectors_0.42.0           
[13] BiocGenerics_0.50.0         MatrixGenerics_1.16.0      
[15] matrixStats_1.3.0          

loaded via a namespace (and not attached):
 [1] pROC_1.18.5               gridExtra_2.3             rlang_1.1.3              
 [4] magrittr_2.0.3            e1071_1.7-14              compiler_4.4.0           
 [7] DelayedMatrixStats_1.26.0 vctrs_0.6.5               reshape2_1.4.4           
[10] stringr_1.5.1             pkgconfig_2.0.3           crayon_1.5.2             
[13] XVector_0.44.0            labeling_0.4.3            scuttle_1.14.0           
[16] utf8_1.2.4                prodlim_2023.08.28        tzdb_0.4.0               
[19] UCSC.utils_1.0.0          bit_4.0.5                 purrr_1.0.2              
[22] xfun_0.43                 beachmat_2.20.0           zlibbioc_1.50.0          
[25] jsonlite_1.8.8            recipes_1.0.10            DelayedArray_0.30.1      
[28] BiocParallel_1.38.0       parallel_4.4.0            R6_2.5.1                 
[31] stringi_1.8.4             parallelly_1.37.1         rpart_4.1.23             
[34] lubridate_1.9.3           Rcpp_1.0.12               iterators_1.0.14         
[37] knitr_1.46                future.apply_1.11.2       readr_2.1.5              
[40] Matrix_1.7-0              splines_4.4.0             nnet_7.3-19              
[43] timechange_0.3.0          tidyselect_1.2.1          rstudioapi_0.16.0        
[46] abind_1.4-5               yaml_2.3.8                timeDate_4032.109        
[49] codetools_0.2-20          listenv_0.9.1             tibble_3.2.1             
[52] plyr_1.8.9                withr_3.0.0               future_1.33.2            
[55] survival_3.5-8            proxy_0.4-27              pillar_1.9.0             
[58] BiocManager_1.30.23       renv_1.0.7                foreach_1.5.2            
[61] generics_0.1.3            vroom_1.6.5               rprojroot_2.0.4          
[64] hms_1.1.3                 sparseMatrixStats_1.16.0  munsell_0.5.1            
[67] scales_1.3.0              globals_0.16.3            class_7.3-22             
[70] glue_1.7.0                tools_4.4.0               data.table_1.15.4        
[73] ModelMetrics_1.2.2.2      gower_1.0.1               grid_4.4.0               
[76] ipred_0.9-14              colorspace_2.1-0          nlme_3.1-164             
[79] GenomeInfoDbData_1.2.12   cli_3.6.2                 fansi_1.0.6              
[82] S4Arrays_1.4.0            lava_1.8.0                dplyr_1.1.4              
[85] gtable_0.3.5              digest_0.6.35             SparseArray_1.4.3        
[88] farver_2.1.1              lifecycle_1.0.4           hardhat_1.3.1            
[91] httr_1.4.7                bit64_4.0.5               MASS_7.3-60.2            
LS0tCnRpdGxlOiAiQ29tcGFyaXNvbiBhbW9uZyBkb3VibGV0IHByZWRpY3Rpb25zIG9uIGJlbmNobWFya2luZyBkYXRhc2V0cyIKYXV0aG9yOiBTdGVwaGFuaWUgSi4gU3BpZWxtYW4KZGF0ZTogImByIFN5cy5EYXRlKClgIgpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19kZXB0aDogNAogICAgY29kZV9mb2xkaW5nOiBoaWRlCi0tLQoKVGhpcyBub3RlYm9vayBjb21wYXJlcyBkb3VibGV0IHJlc3VsdHMgY2FsY3VsYXRlZCBvbiBiZW5jaG1hcmtpbmcgZGF0YXNldHMgdG8gb25lIGFub3RoZXIsIHdpdGggdGhlIHByaW1hcnkgZ29hbCBvZiBhZGRyZXNzaW5nIHRoZXNlIHF1ZXN0aW9uczoKCjEuIERvIG1ldGhvZHMgdGVuZCB0byBwcmVkaWN0IG92ZXJsYXBwaW5nIG9yIGRpc3RpbmN0IHNldHMgb2YgZG91YmxldHMgb24gdGhlIHNhbWUgZGF0YXNldD8KMi4gSXMgdGhlIGNvbnNlbnN1cyBkb3VibGV0IGNhbGwgYWNyb3NzIG1ldGhvZHMgcHJlZGljdGl2ZSBvZiB0aGUgdHJ1ZSBkb3VibGV0IHN0YXR1cz8KSGVyZSwgdGhlICJjb25zZW5zdXMgZG91YmxldHMiIGFyZSB0aG9zZSBkcm9wbGV0cyB3aGljaCBhbGwgbWV0aG9kcyBpZGVudGlmeSBhcyBkb3VibGV0cy4KClRoZXJlIGFyZSBzZXZlcmFsIGl0ZW1zIHRvIGJlYXIgaW4gbWluZCB3aGVuIGludGVycHJldGluZyB0aGVzZSByZXN1bHRzOgoKLSBUaGUgZ3JvdW5kIHRydXRoIGNhbGxzIHRoZW1zZWx2ZXMgdG8gd2hpY2ggd2UgYXJlIGNvbXBhcmluZyBjb25zZW5zdXMgY2FsbHMgbWF5IG5vdCBiZSBlbnRpcmVseSBhY2N1cmF0ZSwgc2luY2UgdGhleSB3ZXJlIGFsc28gY29tcHV0YXRpb25hbGx5IGlkZW50aWZpZWQgZ2VuZXJhbGx5IHdpdGggZGVtdWx0aXBsZXhpbmcgYWxnb3JpdGhtcy4gCi0gYGN4ZHNgLCBhcyB3ZSBhcmUgdXNpbmcgaXQsIGRvZXMgbm90IGhhdmUgYSBzcGVjaWZpYyB0aHJlc2hvbGQgZm9yIGNhbGxpbmcgZHJvcGxldHMuIApCeSBjb250cmFzdCwgYm90aCBgc2NydWJsZXRgIGFuZCBgc2NEYmxGaW5kZXJgIGlkZW50aWZ5IGEgdGhyZXNob2xkIGJhc2VkIG9uIHRoZSBnaXZlbiBkYXRhc2V0IHRoZXkgYXJlIHByb2Nlc3NpbmcuIApUaGlzIG5vdGVib29rIHVzZXMgYSBkb3VibGV0IHRocmVzaG9sZCBvZiBgPj0wLjVgIGZvciBgY3hkc2AsIHdoaWNoIG1heSBub3QgYmUgdW5pdmVyc2FsbHkgc3VpdGFibGUgKHRob3VnaCBjaG9vc2luZyBhIHVuaXZlcnNhbGx5IHN1aXRhYmxlIHRocmVzaG9sZCBpcyBub3QgZWFzeSBlaXRoZXIhKS4KCiMjIFNldHVwCgojIyMgUGFja2FnZXMKCgpgYGB7ciBwYWNrYWdlc30Kc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKHsKICBsaWJyYXJ5KFNpbmdsZUNlbGxFeHBlcmltZW50KQogIGxpYnJhcnkoZ2dwbG90MikKICBsaWJyYXJ5KHBhdGNod29yaykKICBsaWJyYXJ5KGNhcmV0KQogIGxpYnJhcnkoVXBTZXRSKQp9KQoKdGhlbWVfc2V0KHRoZW1lX2J3KCkpCgojIGRlZmluZSB0aHJlc2hvbGQgdXNlZCB0byBjYWxsIGN4ZHMKY3hkc190aHJlc2hvbGQgPC0gMC41CmBgYAoKIyMjIFBhdGhzCgoKYGBge3IgYmFzZSBwYXRoc30KbW9kdWxlX2Jhc2UgPC0gcnByb2pyb290OjpmaW5kX3Jvb3QocnByb2pyb290Ojppc19yZW52X3Byb2plY3QpCmRhdGFfZGlyIDwtIGZpbGUucGF0aChtb2R1bGVfYmFzZSwgInNjcmF0Y2giLCAiYmVuY2htYXJrLWRhdGFzZXRzIikKcmVzdWx0X2RpciA8LSBmaWxlLnBhdGgobW9kdWxlX2Jhc2UsICJyZXN1bHRzIiwgImJlbmNobWFyay1yZXN1bHRzIikKYGBgCgojIyMgRnVuY3Rpb25zCgpgYGB7cn0KcGxvdF9wY2FfY2FsbHMgPC0gZnVuY3Rpb24oZGYsIAogICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvcl9jb2x1bW4sIAogICAgICAgICAgICAgICAgICAgICAgICAgICBwbG90X3R5cGUgPSAiY2FsbHMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICB0aXRsZSkgewogICMgUGxvdCBQQ3MgY29sb3JlZCBieSBlaXRoZXI6CiAgIyBpZiBwbG90X3R5cGUgPT0gImNhbGxzIiwgY29sb3IgYnkgc2luZ2xldCBvciBkb3VibGV0LCBzaG93aW5nIGRvdWJsZXRzIG9uIHRvcAogICMgaWYgcGxvdF90eXBlID09ICJjbGFzcyIsIGNvbG9yIGJ5IHNpbmdsZXQsIGRvdWJsZXQsIG9yIGFtYmlndW91cywgc2hvd2luZyBkb3VibGV0cyAmIGFtYmlndW91cyBvbiB0b3AKICAjIGRmIGlzIGV4cGVjdGVkIHRvIGNvbnRhaW4gY29sdW1ucyBQQzEsIFBDMiwgYGNvbG9yX2NvbHVtbmAsIHdoaWNoIHNob3VsZCBfbm90XyBiZSBwcm92aWRlZCBhcyBhIHN0cmluZwogIHBsb3QgPC0gZ2dwbG90KGRmKSArIAogICAgYWVzKAogICAgICB4ID0gUEMxLCAKICAgICAgeSA9IFBDMiwgCiAgICAgIGNvbG9yID0ge3tjb2xvcl9jb2x1bW59fQogICAgKSArCiAgICBnZW9tX3BvaW50KAogICAgICBzaXplID0gMC43NSwgCiAgICAgIGFscGhhID0gMC42CiAgICApICsKICAgIGdndGl0bGUodGl0bGUpICsKICAgIGd1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemUgPSAyKSkpCiAgCiAgaWYgKHBsb3RfdHlwZSA9PSAiY2FsbHMiKSB7CiAgICBwbG90IDwtIHBsb3QgKyAKICAgICAgc2NhbGVfY29sb3JfbWFudWFsKAogICAgICAgIG5hbWUgPSAiIiwgCiAgICAgICAgdmFsdWVzID0gYygiZG91YmxldCIgPSAiYmxhY2siLCAic2luZ2xldCIgPSAiZ3JheTkwIikpICsgCiAgICAgIGdlb21fcG9pbnQoCiAgICAgICAgZGF0YSA9IGRwbHlyOjpmaWx0ZXIoZGYsIHt7Y29sb3JfY29sdW1ufX0gPT0gImRvdWJsZXQiKSwgCiAgICAgICAgY29sb3IgPSAiYmxhY2siLAogICAgICAgIHNpemUgPSAwLjc1CiAgICAgICkKICB9IGVsc2UgaWYgKHBsb3RfdHlwZSA9PSAiY2xhc3MiKSB7CiAgICBwbG90IDwtIHBsb3QgKyAKICAgICAgc2NhbGVfY29sb3JfbWFudWFsKAogICAgICAgIG5hbWUgPSAiQ29uc2Vuc3VzIiwgCiAgICAgICAgdmFsdWVzID0gYygiYW1iaWd1b3VzIiA9ICJ5ZWxsb3ciLCAKICAgICAgICAgICAgICAgICAgICJkb3VibGV0IiA9ICJibGFjayIsIAogICAgICAgICAgICAgICAgICAgInNpbmdsZXQiID0gImdyYXk5MCIpCiAgICAgICkgKyAKICAgICAgZ2VvbV9wb2ludCgKICAgICAgICBkYXRhID0gZHBseXI6OmZpbHRlcihkZiwgY29uc2Vuc3VzX2NsYXNzICE9ICJzaW5nbGV0IiksIAogICAgICAgIHNpemUgPSAwLjc1LAogICAgICAgIGFscGhhID0gMC43LAogICAgICAgIGFlcyhjb2xvciA9IGNvbnNlbnN1c19jbGFzcyksCiAgICAgICkKICB9IGVsc2UgewogICAgc3RvcCgicGxvdF90eXBlIG11c3QgYmUgJ2NhbGxzJyBvciAnY2xhc3MnIikKICB9CiAgCiAgcmV0dXJuKHBsb3QpCn0KCgpwbG90X3BjYV9tZXRyaWNzIDwtIGZ1bmN0aW9uKGRmLCBjb2xvcl9jb2x1bW4pIHsKICAjIFBsb3QgUENzIGNvbG9yZWQgYnkgcGVyZm9ybWFuY2UgbWV0cmljLCBzaG93aW5nIGZhbHNlIGNhbGxzIG9uIHRvcAogICMgbWV0cmljX2NvbG9ycyBpcyBhIG5hbWVkIHZlY3RvciBvZiBjb2xvcnMgdXNlZCBmb3IgY29sb3JpbmcgdHAvdG4vZnAvZm4KCiAgIyB1c2VkIGluIFBDQSBwbG90cwogIG1ldHJpY19jb2xvcnMgPC0gYygKICAgICJ0cCIgPSAibGlnaHRibHVlIiwKICAgICJ0biIgPSAicGluayIsCiAgICAiZnAiID0gImJsdWUiLAogICAgImZuIiA9ICJmaXJlYnJpY2syIgogICkKCiAgZ2dwbG90KGRmKSArIAogICAgYWVzKHggPSBQQzEsIAogICAgICAgIHkgPSBQQzIsIAogICAgICAgIGNvbG9yID0ge3tjb2xvcl9jb2x1bW59fSkgKwogIGdlb21fcG9pbnQoCiAgICBzaXplID0gMC43NSwgCiAgICBhbHBoYSA9IDAuNgogICkgKyAKICBnZW9tX3BvaW50KAogICAgZGF0YSA9IGRwbHlyOjpmaWx0ZXIoZGYsIHt7Y29sb3JfY29sdW1ufX0gJWluJSBjKCJmcCIsICJmbiIpKSwgCiAgICBzaXplID0gMC43NQogICkgKwogIHNjYWxlX2NvbG9yX21hbnVhbChuYW1lID0gIkNhbGwgdHlwZSIsIHZhbHVlcyA9IG1ldHJpY19jb2xvcnMpICsKICBnZ3RpdGxlKCJDb25zZW5zdXMgY2xhc3MgbWV0cmljcyIpICsKICBndWlkZXMoY29sb3IgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplID0gMikpKQoKfQpgYGAKCgoKIyMgUmVhZCBhbmQgcHJlcGFyZSBpbnB1dCBkYXRhCgpGaXJzdCwgd2UnbGwgcmVhZCBpbiBhbmQgY29tYmluZSBkb3VibGV0IHJlc3VsdHMsIGFzIHdlbGwgYXMgUENBIGNvb3JkaW5hdGVzLCBpbnRvIGEgbGlzdCBvZiBkYXRhIGZyYW1lcyBmb3IgZWFjaCBkYXRhc2V0LiAKCmBgYHtyIGRhdGFzZXQgbmFtZXN9CiMgZmluZCBhbGwgZGF0YXNldCBuYW1lcyB0byBwcm9jZXNzOgpkYXRhc2V0X25hbWVzIDwtIGxpc3QuZmlsZXMocmVzdWx0X2RpciwgcGF0dGVybiA9ICIqX3NjcnVibGV0LnRzdiIpIHw+CiAgc3RyaW5ncjo6c3RyX3JlbW92ZSgiX3NjcnVibGV0LnRzdiIpCmBgYAoKYGBge3IgcmVhZF9kYXRhfQojIFJlYWQgaW4gZGF0YSBmb3IgYW5hbHlzaXMKZG91YmxldF9kZl9saXN0IDwtIGRhdGFzZXRfbmFtZXMgfD4KICBwdXJycjo6bWFwKAogICAgXChkYXRhc2V0KSB7CiAgICAgIAogICAgICBzY2RibF90c3YgPC0gZmlsZS5wYXRoKHJlc3VsdF9kaXIsIGdsdWU6OmdsdWUoIntkYXRhc2V0fV9zY2RibGZpbmRlci50c3YiKSkKICAgICAgc2NydWJfdHN2IDwtIGZpbGUucGF0aChyZXN1bHRfZGlyLCBnbHVlOjpnbHVlKCJ7ZGF0YXNldH1fc2NydWJsZXQudHN2IikpCiAgICAgIHNjZV9maWxlIDwtIGZpbGUucGF0aChkYXRhX2RpciwgZGF0YXNldCwgZ2x1ZTo6Z2x1ZSgie2RhdGFzZXR9X3NjZS5yZHMiKSkKICAgICAgCiAgICAgIHNjZGJsX2RmIDwtIHNjZGJsX3RzdiB8PgogICAgICAgIHJlYWRyOjpyZWFkX3RzdihzaG93X2NvbF90eXBlcyA9IEZBTFNFKSB8PgogICAgICAgIGRwbHlyOjpzZWxlY3QoCiAgICAgICAgICBiYXJjb2RlcywKICAgICAgICAgIGN4ZHNfc2NvcmUsIAogICAgICAgICAgc2NkYmxfc2NvcmUgPSBzY29yZSwgCiAgICAgICAgICBzY2RibF9wcmVkaWN0aW9uICA9IGNsYXNzCiAgICAgICAgKSB8PgogICAgICAgICMgYWRkIGN4ZHMgY2FsbHMgYXQgYGN4ZHNfdGhyZXNob2xkYCB0aHJlc2hvbGQKICAgICAgICBkcGx5cjo6bXV0YXRlKAogICAgICAgICAgY3hkc19wcmVkaWN0aW9uID0gZHBseXI6OmlmX2Vsc2UoCiAgICAgICAgICAgIGN4ZHNfc2NvcmUgPj0gY3hkc190aHJlc2hvbGQsCiAgICAgICAgICAgICJkb3VibGV0IiwKICAgICAgICAgICAgInNpbmdsZXQiCiAgICAgICAgICApCiAgICAgICAgKSAKICAgICAgCiAgICAgIHNjcnViX2RmIDwtIHJlYWRyOjpyZWFkX3RzdihzY3J1Yl90c3YsIHNob3dfY29sX3R5cGVzID0gRkFMU0UpIAoKICAgICAgIyBncmFiIGdyb3VuZCB0cnV0aCBhbmQgUENBIGNvb3JkaW5hdGVzCiAgICAgIHNjZSA8LSByZWFkcjo6cmVhZF9yZHMoc2NlX2ZpbGUpCiAgICAgIAogICAgICBzY3V0dGxlOjptYWtlUGVyQ2VsbERGKHNjZSwgdXNlLmRpbXJlZCA9ICJQQ0EiKSB8PgogICAgICAgIHRpYmJsZTo6cm93bmFtZXNfdG9fY29sdW1uKHZhciA9ICJiYXJjb2RlcyIpIHw+CiAgICAgICAgZHBseXI6OnNlbGVjdChiYXJjb2RlcywKICAgICAgICAgICAgICAgICAgICAgIGdyb3VuZF90cnV0aCA9IGdyb3VuZF90cnV0aF9kb3VibGV0cywgCiAgICAgICAgICAgICAgICAgICAgICBQQzEgPSBQQ0EuMSwgCiAgICAgICAgICAgICAgICAgICAgICBQQzIgPSBQQ0EuMikgfD4gCiAgICAgICAgZHBseXI6OmxlZnRfam9pbigKICAgICAgICAgIHNjcnViX2RmLCAKICAgICAgICAgIGJ5ID0gImJhcmNvZGVzIgogICAgICAgICkgfD4KICAgICAgICBkcGx5cjo6bGVmdF9qb2luKAogICAgICAgICAgc2NkYmxfZGYsIAogICAgICAgICAgYnkgPSAiYmFyY29kZXMiCiAgICAgICAgKSAKICAgIH0gCiAgKSB8PiAKICBwdXJycjo6c2V0X25hbWVzKGRhdGFzZXRfbmFtZXMpCmBgYAoKCgojIyBVcHNldCBwbG90cwoKVGhpcyBzZWN0aW9uIGNvbnRhaW5zIHVwc2V0IHBsb3RzIHRoYXQgc2hvdyBvdmVybGFwIGFjcm9zcyBkb3VibGV0IGNhbGxzIGZyb20gZWFjaCBtZXRob2QsIGRpc3BsYXllZCBmb3IgZWFjaCBkYXRhc2V0LgoKYGBge3J9CmRvdWJsZXRfZGZfbGlzdCB8PgogIHB1cnJyOjppd2FsaygKICAgIFwoZGYsIGRhdGFzZXQpIHsKICAgICAgCiAgICAgIGRvdWJsZXRfYmFyY29kZXMgPC0gbGlzdCgKICAgICAgICAic2NEYmxGaW5kZXIiID0gZGYkYmFyY29kZXNbZGYkc2NkYmxfcHJlZGljdGlvbiA9PSAiZG91YmxldCJdLAogICAgICAgICJzY3J1YmxldCIgICAgPSBkZiRiYXJjb2Rlc1tkZiRzY3J1YmxldF9wcmVkaWN0aW9uID09ICJkb3VibGV0Il0sCiAgICAgICAgImN4ZHMiICAgICAgICA9IGRmJGJhcmNvZGVzW2RmJGN4ZHNfcHJlZGljdGlvbiA9PSAiZG91YmxldCJdCiAgICAgICkKICAgICAgCiAgICAgIFVwU2V0Ujo6dXBzZXQoZnJvbUxpc3QoZG91YmxldF9iYXJjb2RlcyksIG9yZGVyLmJ5ID0gImZyZXEiKSB8PiBwcmludCgpCiAgICAgIGdyaWQ6OmdyaWQudGV4dCggIyBwbG90IHRpdGxlCiAgICAgICAgZGF0YXNldCwKICAgICAgICB4ID0gMC42NSwgCiAgICAgICAgeSA9IDAuOTUsIAogICAgICAgIGdwID0gZ3JpZDo6Z3Bhcihmb250c2l6ZT0xNikKICAgICAgKSAKCiAgICB9CiAgKQoKYGBgCgoKIyMgRXhwbG9yZSBjb25zZW5zdXMgYWNyb3NzIG1ldGhvZHMKCk5leHQsIHdlIHdpbGwgZXhwbG9yZSB0aGUgY29uc2Vuc3VzIG9mIGRvdWJsZXQgcHJlZGljdGlvbnMgYWNyb3NzIG1ldGhvZHMuIApUbyB0aGlzIGVuZCwgd2UnbGwgY3JlYXRlIHNvbWUgbmV3IGNvbHVtbnMgZm9yIGVhY2ggZGF0YXNldDoKCi0gYGNvbnNlbnN1c19jbGFzc2AsIHdoaWNoIHdpbGwgYmUgImRvdWJsZXQiIGlmIGFsbCB0aHJlZSBtZXRob2RzIGFyZSBkb3VibGV0LCAic2luZ2xldCIgaWYgYWxsIHRocmVlIG1ldGhvZHMgYXJlIHNpbmdsZXQsIGFuZCAiYW1iaWd1b3VzIiBpZiB0aGVyZSBhcmUgYW55IGRpc2FncmVlbWVudHMKLSBgY29uc2Vuc3VzX2NhbGxgLCB3aGljaCB3aWxsIGJlICJkb3VibGV0IiBpZiBfYWxsXyBtZXRob2RzIHByZWRpY3QgImRvdWJsZXQsIiBhbmQgInNpbmdsZXQiIG90aGVyd2lzZQotIGBjb25mdXNpb25fY2FsbGAsIHdoaWNoIHdpbGwgY2xhc3NpZnkgdGhlIGNvbnNlbnN1cyBjYWxsIGFzIG9uZSBvZiAidHAiLCAidG4iLCAiZnAiLCBvciAiZm4iICh0cnVlL2ZhbHNlIHBvc2l0aXZlL25lZ2F0aXZlKSBiYXNlZCBvbiBgY29uc2Vuc3VzX2NhbGxgCgpgYGB7cn0KZG91YmxldF9kZl9saXN0IDwtIGRvdWJsZXRfZGZfbGlzdCB8PgogIHB1cnJyOjptYXAoCiAgICBcKGRhdGFzZXRfZGYpIHsKICAgICAgCiAgICAgIGRhdGFzZXRfZGYgfD4KICAgICAgICBkcGx5cjo6cm93d2lzZSgpIHw+CgogICAgICAgIGRwbHlyOjptdXRhdGUoCiAgICAgICAgICAjIEFkZCBjb2x1bW4gYGNvbnNlbnN1c19jYWxsYAogICAgICAgICAgIyAiZG91YmxldCIgaWYgYWxsIHRocmVlIG1ldGhvZHMgYXJlIGRvdWJsZXQsIGFuZCAic2luZ2xldCIgb3RoZXJ3aXNlICAgICAgICAgIAogICAgICAgICAgY29uc2Vuc3VzX2NhbGwgPSBkcGx5cjo6aWZfZWxzZSgKICAgICAgICAgICAgYWxsKGMoc2NkYmxfcHJlZGljdGlvbiwgc2NydWJsZXRfcHJlZGljdGlvbiwgY3hkc19wcmVkaWN0aW9uKSA9PSAiZG91YmxldCIpLAogICAgICAgICAgICAiZG91YmxldCIsIAogICAgICAgICAgICAic2luZ2xldCIKICAgICAgICAgICksCiAgICAgICAgICAjIEFkZCBjb2x1bW4gYGNvbnNlbnN1c19jbGFzc2AKICAgICAgICAgICMgICJkb3VibGV0IiBpZiBhbGwgdGhyZWUgbWV0aG9kcyBhcmUgZG91YmxldCwgInNpbmdsZXQiIGlmIGFsbCB0aHJlZSBtZXRob2RzIGFyZSBzaW5nbGV0LCAKICAgICAgICAgICMgIGFuZCAiYW1iaWd1b3VzIiBpZiB0aGVyZSBhcmUgYW55IGRpc2FncmVlbWVudHMKICAgICAgICAgIGNvbnNlbnN1c19jbGFzcyA9IGRwbHlyOjpjYXNlX3doZW4oCiAgICAgICAgICAgIGNvbnNlbnN1c19jYWxsID09ICJkb3VibGV0IiB+ICJkb3VibGV0IiwKICAgICAgICAgICAgYWxsKGMoc2NkYmxfcHJlZGljdGlvbiwgc2NydWJsZXRfcHJlZGljdGlvbiwgY3hkc19wcmVkaWN0aW9uKSA9PSAic2luZ2xldCIpIH4gInNpbmdsZXQiLCAKICAgICAgICAgICAgLmRlZmF1bHQgPSAiYW1iaWd1b3VzIgogICAgICAgICAgKSwgCiAgICAgICAgICAjIEFkZCBjb2x1bW4gYGNvbmZ1c2lvbl9jYWxsYAogICAgICAgICAgIyAgdHAvdG4vZnAvZm4gYmFzZWQgb24gdGhlIGBjb25zZW5zdXNfY2FsbGAgY29sdW1uCiAgICAgICAgICBjb25mdXNpb25fY2FsbCA9IGRwbHlyOjpjYXNlX3doZW4oCiAgICAgICAgICAgIGNvbnNlbnN1c19jYWxsID09ICJkb3VibGV0IiAmJiBncm91bmRfdHJ1dGggPT0gImRvdWJsZXQiIH4gInRwIiwKICAgICAgICAgICAgY29uc2Vuc3VzX2NhbGwgPT0gInNpbmdsZXQiICYmIGdyb3VuZF90cnV0aCA9PSAic2luZ2xldCIgfiAidG4iLAogICAgICAgICAgICBjb25zZW5zdXNfY2FsbCA9PSAiZG91YmxldCIgJiYgZ3JvdW5kX3RydXRoID09ICJzaW5nbGV0IiB+ICJmcCIsCiAgICAgICAgICAgIGNvbnNlbnN1c19jYWxsID09ICJzaW5nbGV0IiAmJiBncm91bmRfdHJ1dGggPT0gImRvdWJsZXQiIH4gImZuIgogICAgICAgICAgKQogICAgICAgICkKICAgICAgfQogICkgCmBgYAogICAgICAKCiMjIyBQQ0EKClRoaXMgc2VjdGlvbiBwbG90cyB0aGUgUENBIGZvciBlYWNoIGRhdGFzZXQsIGNsb2Nrd2lzZSBmcm9tIHRoZSB0b3AgbGVmdDoKCjEuIGBzY0RibEZpbmRlcmAgc2luZ2xldC9kb3VibGV0IGNhbGxzCjIuIGBzY3J1YmxldGAgc2luZ2xldC9kb3VibGV0IGNhbGxzCjMuIGBjeGRzYCBzaW5nbGV0L2RvdWJsZXQgY2FsbHMgCjQuIEdyb3VuZCB0cnV0aCBzaW5nbGUvZG91YmxldHMKNS4gQ29uc2Vuc3VzIF9jYWxsXyBkcm9wbGV0cyBhcmUgZWl0aGVyICJkb3VibGV0IiBvciAic2luZ2xldCIKNi4gQ29uc2Vuc3VzIF9jbGFzc186IGRyb3BsZXRzIGFyZSBlaXRoZXIgImRvdWJsZXQiLCAic2luZ2xldCIsIG9yICJhbWJpZ3VvdXMiCjcuIE1ldHJpY3MgYmFzZWQgb24gdGhlIGNvbnNlbnN1cyBjYWxsCgotIEZvciB0aGUgZmlyc3QgZml2ZSBwYW5lbHMsIGRvdWJsZXRzIGFyZSBzaG93biBpbiBibGFjayBhbmQgc2luZ2xldHMgaW4gbGlnaHQgZ3JheS4KLSBJbiB0aGUgc2l4dGggcGFuZWwgc2hvd2luZyB0aGUgY29uc2Vuc3VzIGNsYXNzLCBkb3VibGV0cyBhcmUgc2hvd24gaW4gYmxhY2ssIHNpbmdsZXRzIGluIGxpZ2h0IGdyYXksIGFuZCBhbWJpZ3VvdXMgYXJlIHllbGxvdwotIEluIHRoZSBzZXZlbnRoIHBhbmVsIHNob3dpbmcgbWV0cmljcywgY29sb3JzIGFyZToKICAtIFRydWUgcG9zaXRpdmU6IGxpZ2h0IGJsdWUKICAtIFRydWUgbmVnYXRpdmU6IHBpbmsKICAtIEZhbHNlIHBvc2l0aXZlOiBibHVlCiAgLSBGYWxzZSBuZWdhdGl2ZTogcmVkCiAgICAKYGBge3IsIGZpZy53aWR0aCA9IDEyLCBmaWcuaGVpZ2h0ID0gOX0KcGNfY29sb3JfY29sdW1ucyA8LSBjKCJzY0RibEZpbmRlciBwcmVkaWN0aW9uIiA9ICJzY2RibF9wcmVkaWN0aW9uIiwKICAgICAgICAgICAgICAgICAgICAgICJzY3J1YmxldCBwcmVkaWN0aW9uIiA9ICJzY3J1YmxldF9wcmVkaWN0aW9uIiwKICAgICAgICAgICAgICAgICAgICAgICJjeGRzIHByZWRpY3Rpb24iID0gImN4ZHNfcHJlZGljdGlvbiIsCiAgICAgICAgICAgICAgICAgICAgICAiR3JvdW5kIHRydXRoIiA9ICJncm91bmRfdHJ1dGgiLCAKICAgICAgICAgICAgICAgICAgICAgICJDb25zZW5zdXMgY2FsbCIgPSAiY29uc2Vuc3VzX2NhbGwiKQpkb3VibGV0X2RmX2xpc3QgfD4KICBwdXJycjo6aW1hcCgKICAgIFwoZGYsIGRhdGFzZXQpIHsKICAKICAgICAgcGNfcGxvdF9saXN0IDwtIHBjX2NvbG9yX2NvbHVtbnMgfD4gCiAgICAgICAgcHVycnI6OmltYXAoCiAgICAgICAgICBcKGNvbG9yX2NvbHVtbiwgcGxvdF90aXRsZSkgewogICAgICAgICAgICBwbG90X3BjYV9jYWxscygKICAgICAgICAgICAgICBkZiwgCiAgICAgICAgICAgICAgY29sb3JfY29sdW1uID0gISFzeW0oY29sb3JfY29sdW1uKSwgCiAgICAgICAgICAgICAgcGxvdF90eXBlID0gImNhbGxzIiwKICAgICAgICAgICAgICB0aXRsZSA9IHBsb3RfdGl0bGUKICAgICAgICAgICAgKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIil9CiAgICAgICAgKSAKCiAgICAgICMgcGxvdCBmb3IgY29uc2Vuc3VzX2NsYXNzLCB3aGljaCBuZWVkcyB0aHJlZSBjb2xvcnMKICAgICAgIyBub3RlIHRoYXQgdGhlIGxpc3QgbmFtZSBpc24ndCBhY3R1YWxseSB1c2VkIGhlcmUKICAgICAgcGNfcGxvdF9saXN0JGNvbnNlbnN1c19jbGFzcyA8LSBwbG90X3BjYV9jYWxscygKICAgICAgICBkZiwgCiAgICAgICAgY29sb3JfY29sdW1uID0gY29uc2Vuc3VzX2NsYXNzLAogICAgICAgIHBsb3RfdHlwZSA9ICJjbGFzcyIsCiAgICAgICAgdGl0bGUgPSAiQ29uc2Vuc3VzIGNsYXNzIgogICAgICApCiAgICAgIAogICAgICAjIHBsb3QgbWV0cmljcwogICAgICBwY19wbG90X2xpc3QkbWV0cmljcyA8LSBwbG90X3BjYV9tZXRyaWNzKAogICAgICAgIGRmLCAKICAgICAgICBjb2xvcl9jb2x1bW4gPSBjb25mdXNpb25fY2FsbAogICAgICApCgogICAgICBwYXRjaHdvcms6OndyYXBfcGxvdHMocGNfcGxvdF9saXN0KSArCiAgICAgICAgcGxvdF9hbm5vdGF0aW9uKAogICAgICAgICAgICBnbHVlOjpnbHVlKCJQQ0EgZm9yIHtkYXRhc2V0fSIpLCAKICAgICAgICAgICAgdGhlbWUgPSB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNikpKSArIAogICAgICAgIHBsb3RfbGF5b3V0KGd1aWRlcyA9ICJjb2xsZWN0IikKICAgIH0KICApCmBgYAoKCiMjIyBQZXJmb3JtYW5jZSBtZXRyaWNzIAoKVGhpcyBzZWN0aW9uIHNob3dzIGEgdGFibGUgb2YgdGhlIGNvbnNlbnN1cyBjbGFzcyBjb3VudHMgYW5kIGNhbGN1bGF0ZXMgYSBjb25mdXNpb24gbWF0cml4IHdpdGggYXNzb2NpYXRlZCBzdGF0aXN0aWNzIGZyb20gdGhlIGNvbnNlbnN1cyBjYWxsczogCgpgYGB7cn0KbWV0cmljX2RmIDwtIGRvdWJsZXRfZGZfbGlzdCB8PgogIHB1cnJyOjppbWFwKCAKICAgIFwoZGYsIGRhdGFzZXQpIHsKICAgICAgICBwcmludChnbHVlOjpnbHVlKCI9PT09PT09PT09PT09PT09PT09PT09PT0ge2RhdGFzZXR9ID09PT09PT09PT09PT09PT09PT09PT09PSIpKQogICAgICAKICAgICAgICBjYXQoIlRhYmxlIG9mIGNvbnNlbnN1cyBjbGFzcyBjb3VudHM6IikKICAgICAgICBwcmludCh0YWJsZShkZiRjb25zZW5zdXNfY2xhc3MpKQogICAgICAgIAogICAgICAgIGNhdCgiXG5cbiIpCiAgICAgICAgCiAgICAgICAgY29uZnVzaW9uX3Jlc3VsdCA8LSBjYXJldDo6Y29uZnVzaW9uTWF0cml4KAogICAgICAgICAgIyB0cnV0aCBzaG91bGQgYmUgZmlyc3QKICAgICAgICAgIHRhYmxlKAogICAgICAgICAgICAiVHJ1dGgiID0gZGYkZ3JvdW5kX3RydXRoLAogICAgICAgICAgICAiQ29uc2Vuc3VzIHByZWRpY3Rpb24iID0gZGYkY29uc2Vuc3VzX2NhbGwKICAgICAgICAgICksIAogICAgICAgICAgcG9zaXRpdmUgPSAiZG91YmxldCIKICAgICAgICApIAogICAgICAgIAogICAgICAgIHByaW50KGNvbmZ1c2lvbl9yZXN1bHQpCiAgICAgICAgCiAgICAgICAgIyBFeHRyYWN0IGluZm9ybWF0aW9uIHdlIHdhbnQgdG8gcHJlc2VudCBsYXRlciBpbiBhIHRhYmxlCiAgICAgICAgdGliYmxlOjp0aWJibGUoCiAgICAgICAgICAiRGF0YXNldCBuYW1lIiA9IGRhdGFzZXQsCiAgICAgICAgICAiS2FwcGEiID0gcm91bmQoY29uZnVzaW9uX3Jlc3VsdCRvdmVyYWxsWyJLYXBwYSJdLCAzKSwgCiAgICAgICAgICAiQmFsYW5jZWQgYWNjdXJhY3kiID0gcm91bmQoY29uZnVzaW9uX3Jlc3VsdCRieUNsYXNzWyJCYWxhbmNlZCBBY2N1cmFjeSJdLCAzKQogICAgICAgICkKICAgIH0KICApIHw+CiAgZHBseXI6OmJpbmRfcm93cygpCmBgYAoKCgojIyMgQ29uY2x1c2lvbnMKCk92ZXJhbGwsIG1ldGhvZHMgZG8gbm90IGhhdmUgc3Vic3RhbnRpYWwgb3ZlcmxhcCB3aXRoIGVhY2ggb3RoZXIuIApUaGV5IGVhY2ggdGVuZCB0byBkZXRlY3QgZGlmZmVyZW50IHNldHMgb2YgZG91YmxldHMsIGxlYWRpbmcgdG8gZmFpcmx5IHNtYWxsIHNldHMgb2YgY29uc2Vuc3VzIGRvdWJsZXRzLiAKRnVydGhlciwgdGhlIGNvbnNlbnN1cyBkb3VibGV0cyBjYWxsZWQgYnkgYWxsIHRocmVlIG1ldGhvZHMgaGF2ZSBzb21lLCBidXQgbm90IHN1YnN0YW50aWFsLCBvdmVybGFwIHdpdGggdGhlIGdyb3VuZCB0cnV0aC4gCgpGb3IgdGhyZWUgb3V0IG9mIGZvdXIgZGF0YXNldHMsIGBzY0RibEZpbmRlcmAgcHJlZGljdHMgYSBtdWNoIGxhcmdlciBudW1iZXIgb2YgZG91YmxldHMgY29tcGFyZWQgdG8gb3RoZXIgbWV0aG9kcy4gCgpCYXNlZCBvbiB0aGUgUENBcywgaXQgYWRkaXRpb25hbGx5IGxvb2tzIGxpa2UgdGhlcmUgYXJlIG1hbnkgbW9yZSBmYWxzZSBuZWdhdGl2ZXMgaW4gdGhlIGNvbnNlbnN1cyBwcmVkaWN0aW9uIHRoYXQgYGN4ZHNgIGFwcGVhcnMgdG8gY2FwdHVyZSBidXQgb3RoZXIgbWV0aG9kcyBkbyBub3QuCgpUaGUgdGFibGUgYmVsb3cgc3VtbWFyaXplcyBwZXJmb3JtYW5jZSBvZiB0aGUgImNvbnNlbnN1cyBjYWxsZXIiLiAKTm90ZSB0aGF0LCBpbiB0aGUgW2JlbmNobWFya2luZyBwYXBlciB0aGVzZSBkYXRhc2V0cyB3ZXJlIG9yaWdpbmFsbHkgYW5hbHl6ZWQgaW5dKGh0dHBzOi8vZG9pLm9yZy8xMC4xMDE2L2ouY2Vscy4yMDIwLjExLjAwOCksIGBobS02a2Agd2FzIG9ic2VydmVkIHRvIGJlIG9uZSBvZiB0aGUgImVhc2llc3QiIGRhdGFzZXRzIHRvIGNsYXNzaWZ5IGFjcm9zcyBtZXRob2RzLiAgCkNvbnNpc3RlbnQgd2l0aCB0aGF0IG9ic2VydmF0aW9uLCBpdCBoYXMgdGhlIGhpZ2hlc3QgYGthcHBhYCB2YWx1ZSBoZXJlLgoKYGBge3J9Cm1ldHJpY19kZgpgYGAKCiMjIEV4cGxvcmUgY29uc2Vuc3VzIG9mIGBzY0RibEZpbmRlcmAgYW5kIGBjeGRzYAoKVGhlIGFib3ZlIGFuYWx5c2lzIHN1Z2dlc3RzIHRoYXQgYHNjcnVibGV0YCBtYXkgYmUgbW9yZSBjb25zZXJ2YXRpdmUgdGhhbiB0aGUgb3RoZXIgdHdvIG1ldGhvZHMsIHNvIGluY2x1ZGluZyBpdCBpbiBhIGNvbnNlbnN1cyBtYXkgYmUgaW5jcmVhc2luZyB0aGUgbnVtYmVyIG9mIGZhbHNlIHBvc2l0aXZlcy4KSGVyZSwgd2UnbGwgcmVwZWF0IHRoZSBzYW1lIGNvbnNlbnN1cyBhbmFseXNlcywgYnV0IGNvbnNpZGVyaW5nIF9vbmx5XyBgc2NEYmxGaW5kZXJgIGFuZCBgY3hkc2AuClNpbmNlIHRoZXJlIGFyZSBvbmx5IHR3byBtZXRob2RzIGhlcmUsIHdlIHdpbGwgY3JlYXRlIHR3byBjb2x1bW5zIGZvciB0aGlzIGRhdGE6CgojIyMgUHJlcGFyZSBkYXRhCgpgYGB7cn0KIyBtb2RpZnkgZXhpc3RpbmcgY29uc2Vuc3VzIGNvbHVtbnMgdG8gb25seSBjb25zaWRlciAyIG1ldGhvZHMKZG91YmxldF9kZl9saXN0IDwtIGRvdWJsZXRfZGZfbGlzdCB8PgogIHB1cnJyOjptYXAoCiAgICBcKGRhdGFzZXRfZGYpIHsKICAgICAgCiAgICAgIGRhdGFzZXRfZGYgPC0gZGF0YXNldF9kZiB8PgogICAgICAgIGRwbHlyOjpyb3d3aXNlKCkgfD4KICAgICAgICAjIFVwZGF0ZSBjb2x1bW4gYGNvbnNlbnN1c19jYWxsYAogICAgICAgIGRwbHlyOjptdXRhdGUoCiAgICAgICAgICBjb25zZW5zdXNfY2FsbCA9IGRwbHlyOjppZl9lbHNlKAogICAgICAgICAgICBhbGwoYyhzY2RibF9wcmVkaWN0aW9uLCBjeGRzX3ByZWRpY3Rpb24pID09ICJkb3VibGV0IiksCiAgICAgICAgICAgICJkb3VibGV0IiwgCiAgICAgICAgICAgICJzaW5nbGV0IgogICAgICAgICAgKQogICAgICAgICkgfD4KICAgICAgICAjIEFkZCBVcGRhdGUgYGNvbnNlbnN1c19jbGFzc2AKICAgICAgICBkcGx5cjo6bXV0YXRlKAogICAgICAgICAgY29uc2Vuc3VzX2NsYXNzID0gZHBseXI6OmNhc2Vfd2hlbigKICAgICAgICAgICAgY29uc2Vuc3VzX2NhbGwgPT0gImRvdWJsZXQiIH4gImRvdWJsZXQiLAogICAgICAgICAgICBhbGwoYyhzY2RibF9wcmVkaWN0aW9uLCBjeGRzX3ByZWRpY3Rpb24pID09ICJzaW5nbGV0IikgfiAic2luZ2xldCIsIAogICAgICAgICAgICAuZGVmYXVsdCA9ICJhbWJpZ3VvdXMiCiAgICAgICAgICApCiAgICAgICAgICkgfD4KICAgICAgICAjIEFkZCBVcGRhdGUgYGNvbmZ1c2lvbl9jYWxsYAogICAgICAgIGRwbHlyOjptdXRhdGUoCiAgICAgICAgICBjb25mdXNpb25fY2FsbCA9IGRwbHlyOjpjYXNlX3doZW4oCiAgICAgICAgICAgIGNvbnNlbnN1c19jYWxsID09ICJkb3VibGV0IiAmJiBncm91bmRfdHJ1dGggPT0gImRvdWJsZXQiIH4gInRwIiwKICAgICAgICAgICAgY29uc2Vuc3VzX2NhbGwgPT0gInNpbmdsZXQiICYmIGdyb3VuZF90cnV0aCA9PSAic2luZ2xldCIgfiAidG4iLAogICAgICAgICAgICBjb25zZW5zdXNfY2FsbCA9PSAiZG91YmxldCIgJiYgZ3JvdW5kX3RydXRoID09ICJzaW5nbGV0IiB+ICJmcCIsCiAgICAgICAgICAgIGNvbnNlbnN1c19jYWxsID09ICJzaW5nbGV0IiAmJiBncm91bmRfdHJ1dGggPT0gImRvdWJsZXQiIH4gImZuIgogICAgICAgICAgKQogICAgICAgICkKICAgICAgCiAgICAgIHJldHVybihkYXRhc2V0X2RmKQogICAgfQogICkgfD4gCiAgcHVycnI6OnNldF9uYW1lcyhkYXRhc2V0X25hbWVzKQpgYGAKCgojIyMgUENBCgpUaGlzIHNlY3Rpb24gcGxvdHMgdGhlIFBDQSBmb3IgZWFjaCBkYXRhc2V0LCBjbG9ja3dpc2UgZnJvbSB0aGUgdG9wIGxlZnQ6CgoxLiBgc2NEYmxGaW5kZXJgIHNpbmdsZXQvZG91YmxldCBjYWxscwoyLiBgY3hkc2Agc2luZ2xldC9kb3VibGV0IGNhbGxzCjMuIEdyb3VuZCB0cnV0aCBzaW5nbGUvZG91YmxldHMKNC4gQ29uc2Vuc3VzIF9jYWxsXyBkcm9wbGV0cyBhcmUgZWl0aGVyICJkb3VibGV0IiBvciAic2luZ2xldCIKNS4gQ29uc2Vuc3VzIF9jbGFzc186IGRyb3BsZXRzIGFyZSBlaXRoZXIgImRvdWJsZXQiLCAic2luZ2xldCIsIG9yICJhbWJpZ3VvdXMiCjYuIE1ldHJpY3MgYmFzZWQgb24gdGhlIGNvbnNlbnN1cyBjYWxsCgoKYGBge3IsIGZpZy53aWR0aCA9IDEyLCBmaWcuaGVpZ2h0ID0gNn0KcGNfY29sb3JfY29sdW1ucyA8LSBjKCJzY0RibEZpbmRlciBwcmVkaWN0aW9uIiA9ICJzY2RibF9wcmVkaWN0aW9uIiwKICAgICAgICAgICAgICAgICAgICAgICJjeGRzIHByZWRpY3Rpb24iID0gImN4ZHNfcHJlZGljdGlvbiIsCiAgICAgICAgICAgICAgICAgICAgICAiR3JvdW5kIHRydXRoIiA9ICJncm91bmRfdHJ1dGgiLCAKICAgICAgICAgICAgICAgICAgICAgICJDb25zZW5zdXMgY2FsbCIgPSAiY29uc2Vuc3VzX2NhbGwiKQoKZG91YmxldF9kZl9saXN0IHw+CiAgcHVycnI6OmltYXAoCiAgICBcKGRmLCBkYXRhc2V0KSB7CiAgICAgIAogICAgICBwY19wbG90X2xpc3QgPC0gcGNfY29sb3JfY29sdW1ucyB8PiAKICAgICAgICBwdXJycjo6aW1hcCgKICAgICAgICAgIFwoY29sb3JfY29sdW1uLCBwbG90X3RpdGxlKSB7CiAgICAgICAgICAgIHBsb3RfcGNhX2NhbGxzKAogICAgICAgICAgICAgIGRmLCAKICAgICAgICAgICAgICBjb2xvcl9jb2x1bW4gPSAhIXN5bShjb2xvcl9jb2x1bW4pLCAKICAgICAgICAgICAgICBwbG90X3R5cGUgPSAiY2FsbHMiLAogICAgICAgICAgICAgIHRpdGxlID0gcGxvdF90aXRsZQogICAgICAgICAgICApICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKX0KICAgICAgICApIAogICAgICAjIHBsb3QgZm9yIGNvbnNlbnN1c19jbGFzcywgd2hpY2ggbmVlZHMgdGhyZWUgY29sb3JzCiAgICAgICMgbm90ZSB0aGF0IHRoZSBsaXN0IG5hbWUgaXNuJ3QgYWN0dWFsbHkgdXNlZCBoZXJlCiAgICAgIHBjX3Bsb3RfbGlzdCRjb25zZW5zdXNfY2xhc3MgPC0gcGxvdF9wY2FfY2FsbHMoCiAgICAgICAgZGYsIAogICAgICAgIGNvbG9yX2NvbHVtbiA9IGNvbnNlbnN1c19jbGFzcywKICAgICAgICBwbG90X3R5cGUgPSAiY2xhc3MiLAogICAgICAgIHRpdGxlID0gIkNvbnNlbnN1cyBjbGFzcyIKICAgICAgKQoKICAgICAgIyBwbG90IG1ldHJpY3MKICAgICAgcGNfcGxvdF9saXN0JG1ldHJpY3MgPC0gcGxvdF9wY2FfbWV0cmljcygKICAgICAgICBkZiwgCiAgICAgICAgY29sb3JfY29sdW1uID0gY29uZnVzaW9uX2NhbGwKICAgICAgKQogICAgICAKICAgICAgcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKHBjX3Bsb3RfbGlzdCkgKwogICAgICAgIHBsb3RfYW5ub3RhdGlvbigKICAgICAgICAgICAgZ2x1ZTo6Z2x1ZSgiUENBIGZvciB7ZGF0YXNldH0iKSwgCiAgICAgICAgICAgIHRoZW1lID0gdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTYpKSkgKyAKICAgICAgICBwbG90X2xheW91dChndWlkZXMgPSAiY29sbGVjdCIpCiAgICB9CiAgKQpgYGAKCgojIyMgUGVyZm9ybWFuY2UgbWV0cmljcyAKClRoaXMgc2VjdGlvbiBzaG93cyBhIHRhYmxlIG9mIHRoZSBjb25zZW5zdXMgY2xhc3MgY291bnRzIGFuZCBjYWxjdWxhdGVzIGEgY29uZnVzaW9uIG1hdHJpeCB3aXRoIGFzc29jaWF0ZWQgc3RhdGlzdGljcyBmcm9tIHRoZSBjb25zZW5zdXMgY2FsbHM6IAoKYGBge3J9Cm1ldHJpY19kZiA8LSBkb3VibGV0X2RmX2xpc3QgfD4KICBwdXJycjo6aW1hcCggCiAgICBcKGRmLCBkYXRhc2V0KSB7CiAgICAgICAgcHJpbnQoZ2x1ZTo6Z2x1ZSgiPT09PT09PT09PT09PT09PT09PT09PT09IHtkYXRhc2V0fSA9PT09PT09PT09PT09PT09PT09PT09PT0iKSkKICAgICAgCiAgICAgICAgY2F0KCJUYWJsZSBvZiBjb25zZW5zdXMgY2xhc3MgY291bnRzOiIpCiAgICAgICAgcHJpbnQodGFibGUoZGYkY29uc2Vuc3VzX2NsYXNzKSkKICAgICAgICAKICAgICAgICBjYXQoIlxuXG4iKQogICAgICAgIAogICAgICAgIGNvbmZ1c2lvbl9yZXN1bHQgPC0gY2FyZXQ6OmNvbmZ1c2lvbk1hdHJpeCgKICAgICAgICAgICMgdHJ1dGggc2hvdWxkIGJlIGZpcnN0CiAgICAgICAgICB0YWJsZSgKICAgICAgICAgICAgIlRydXRoIiA9IGRmJGdyb3VuZF90cnV0aCwKICAgICAgICAgICAgIkNvbnNlbnN1cyBwcmVkaWN0aW9uIiA9IGRmJGNvbnNlbnN1c19jYWxsCiAgICAgICAgICApLCAKICAgICAgICAgIHBvc2l0aXZlID0gImRvdWJsZXQiCiAgICAgICAgKSAKICAgICAgICAKICAgICAgICBwcmludChjb25mdXNpb25fcmVzdWx0KQogICAgICAgIAogICAgICAgICMgRXh0cmFjdCBpbmZvcm1hdGlvbiB3ZSB3YW50IHRvIHByZXNlbnQgbGF0ZXIgaW4gYSB0YWJsZQogICAgICAgIHRpYmJsZTo6dGliYmxlKAogICAgICAgICAgIkRhdGFzZXQgbmFtZSIgPSBkYXRhc2V0LAogICAgICAgICAgIkthcHBhIiA9IHJvdW5kKGNvbmZ1c2lvbl9yZXN1bHQkb3ZlcmFsbFsiS2FwcGEiXSwgMyksIAogICAgICAgICAgIkJhbGFuY2VkIGFjY3VyYWN5IiA9IHJvdW5kKGNvbmZ1c2lvbl9yZXN1bHQkYnlDbGFzc1siQmFsYW5jZWQgQWNjdXJhY3kiXSwgMykKICAgICAgICApCiAgICB9CiAgKSB8PgogIGRwbHlyOjpiaW5kX3Jvd3MoKQpgYGAKCiMjIyBDb25jbHVzaW9ucwoKQ29uc2lkZXJpbmcgb25seSBgc2NEYmxGaW5kZXJgIGFuZCBgY3hkc2AsIHdlIHNlZSBzZXZlcmFsIHBhcnRpY3VsYXIgZGlmZmVyZW5jZXMgaW4gc3RhdGlzdGljczoKCi0gQmFsYW5jZWQgYWNjdXJhY3kgZm9yIGBobS02a2AgYW5kIGBITUVDLW9yaWctTVVMVElgIGFyZSBhYm91dCB0aGUgc2FtZSBoZXJlIGFzIGZvciB0aGUgY29uc2Vuc3VzIGFtb25nIGFsbCBtZXRob2RzLCBidXQgYWNjdXJhY3kgaGFzIHN1YnN0YW50aWFsbHkgX2RlY3JlYXNlZF8gZm9yIGBwYm1jLTFCLWRtYCBhbmQgYHBkeC1NVUxUSTFgCi0gQnkgY29udHJhc3QsIGthcHBhIHZhbHVlcyBoYXZlIGluY3JlYXNlZCBmb3IgYWxsIGRhdGFzZXRzLCB3aXRoIHRoZSBtb3N0IG1hcmtlZCBpbmNyZWFzZSBmb3IgYHBkeC1NVUxUSWAuCgpFdmVuIHNvLCBvbmx5IGBobS02a2AgKHRoZSAiZWFzeSIgZGF0YXNldCkgaGFzIGJvdGggYSBoaWdoIGthcHBhIGFuZCBoaWdoIGJhbGFuY2VkIGFjY3VyYWN5LgpXaGlsZSBgSE1FQy1vcmlnLU1VTFRJYCdzIGJhbGFuY2VkIGFjY3VyYWN5IGlzIGZhaXJseSBoaWdoLCBpdHMga2FwcGEgdmFsdWUgaXMgbm90LgoKCmBgYHtyfQptZXRyaWNfZGYKYGBgCgoKCiMjIFNlc3Npb24gSW5mbwoKYGBge3Igc2Vzc2lvbiBpbmZvfQojIHJlY29yZCB0aGUgdmVyc2lvbnMgb2YgdGhlIHBhY2thZ2VzIHVzZWQgaW4gdGhpcyBhbmFseXNpcyBhbmQgb3RoZXIgZW52aXJvbm1lbnQgaW5mb3JtYXRpb24Kc2Vzc2lvbkluZm8oKQpgYGAK